// SPDX-License-Identifier: Mulan PSL v2
/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * This software is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

pub mod error;
pub mod framer;
pub mod transport;

pub mod bytewise;
pub mod message;

pub mod peer;

#[cfg(test)]
mod tests {
    use std::{sync::Once, thread, time::Duration};

    use tracing::debug;

    use crate::ipc::{
        framer::LengthPrefixFramer,
        message::{Argument, ArgumentFlag, Request, Response},
        peer::{Client, Server},
        transport::shmem::{unique_shmem_name, ShmemTransport},
    };

    fn init_test_logger() {
        static INIT_LOGGER: Once = Once::new();

        INIT_LOGGER.call_once(|| {
            let _ = tracing_subscriber::fmt()
                .with_max_level(tracing::Level::DEBUG)
                .with_thread_ids(true)
                .with_thread_names(false)
                .with_file(false)
                .with_target(false)
                .with_writer(std::io::stdout)
                .try_init();
        });
    }

    #[test]
    fn test_invoke() {
        mod method_id {
            pub const ADD_U64: u64 = 0xCAFE;
            pub const SHUTDOWN: u64 = 0xFFFF;
        }

        const CONN_TIMEOUT: Duration = Duration::from_millis(200);

        self::init_test_logger();

        let transport = ShmemTransport::new(4096 * 1024);
        let framer = LengthPrefixFramer::new(4096);
        let addr = unique_shmem_name();

        let server = Server::create(&transport, framer, &addr).unwrap();
        debug!("{:#?}", server);

        let client = Client::connect(&transport, framer, &addr, CONN_TIMEOUT).unwrap();
        debug!("{:#?}", client);

        let server_thread = std::thread::spawn(move || {
            debug!("[Server] Thread started");

            while let Ok(Some(request)) = server.receive_message::<Request>() {
                debug!(
                    "[Server] Received request: request_id={}, method_id={}, argc={}",
                    request.request_id(),
                    request.method_id(),
                    request.argc()
                );

                match request.method_id() {
                    method_id::ADD_U64 => {
                        let lhs = request.args()[0]
                            .downcast::<u64>()
                            .expect("Invalid argument type");
                        let rhs = request.args()[1]
                            .downcast::<u64>()
                            .expect("Invalid argument type");

                        let result = lhs + rhs;
                        let response = Response::with_request(
                            &request,
                            Argument::from_ref(&result, ArgumentFlag::default()),
                        );
                        debug!(
                            "[Server] Sending response: request_id={}, method_id={}",
                            response.request_id(),
                            response.method_id(),
                        );
                        server
                            .send_message(&response)
                            .expect("Failed to send response");
                    }
                    method_id::SHUTDOWN => {
                        let response = Response::with_request(&request, Argument::empty());
                        debug!(
                            "[Server] Sending response: request_id={}, method_id={}",
                            response.request_id(),
                            response.method_id(),
                        );
                        server
                            .send_message(&response)
                            .expect("Failed to send response");

                        debug!("[Server] Received shutdown request, shutting down...");
                        break;
                    }
                    _ => {
                        debug!(
                            "[Server] Request {}: Unknown method {}",
                            request.request_id(),
                            request.method_id(),
                        );
                        break;
                    }
                }
            }

            thread::sleep(Duration::from_millis(200));
            debug!("[Server] Thread finished");
        });

        {
            let mut value = 0u64;
            for i in 1..=100u64 {
                debug!("[Client] Invoking 'server::add_u64({}, {})'...", value, i);
                let request = Request::with_args(
                    method_id::ADD_U64,
                    vec![
                        Argument::from_ref(&value, ArgumentFlag::ARG_IN),
                        Argument::from_ref(&i, ArgumentFlag::ARG_IN),
                    ],
                );
                let response = client.invoke(&request).expect("Invoke failed");

                let ret_val = response
                    .ret_value()
                    .downcast::<u64>()
                    .expect("Invalid argument type");
                debug!(
                    "[Client] Result 'server::add_u64({}, {})' is {}",
                    value, i, ret_val
                );

                value = ret_val;
            }
        }

        {
            let request = Request::empty(method_id::SHUTDOWN);
            debug!("[Client] Invoking 'server::shutdown()'...");
            let response = client.invoke(&request).expect("Invoke failed");
            let ret_val = response
                .ret_value()
                .downcast::<()>()
                .expect("Invalid argument type");
            debug!("[Client] Result 'server::shutdown()' is {:?}", ret_val);
        }

        let _ = server_thread.join();
    }
}
